<?php
/**
 * PHPLinq
 *
 * Copyright (c) 2008 PHPLinq
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @category   PHPLinq
 * @package    PHPLinq
 * @copyright  Copyright (c) 2008 PHPLinq (http://www.codeplex.com/PHPLinq)
 * @license    http://www.gnu.org/licenses/lgpl.txt	LGPL
 * @version    ##VERSION##, ##DATE##
 */


/** PHPLinq_ILinqProvider */
require_once('PHPLinq/ILinqProvider.php');

/** PHPLinq_Expression */
require_once('PHPLinq/Expression.php');

/** PHPLinq_OrderByExpression */
require_once('PHPLinq/OrderByExpression.php');


/**
 * PHPLinq_LinqToObjects
 *
 * @category   PHPLinq
 * @package    PHPLinq
 * @copyright  Copyright (c) 2008 PHPLinq (http://www.codeplex.com/PHPLinq)
 */
class PHPLinq_LinqToObjects implements PHPLinq_ILinqProvider {
	/**
	 * Default variable name
	 *
	 * @var string
	 */
	private $_from = '';
	
	/**
	 * Data source
	 *
	 * @var mixed
	 */
	private $_data = null;
	
	/**
	 * Where expression
	 *
	 * @var PHPLinq_Expression
	 */
	private $_where = null;
	
	/**
	 * Take n elements
	 *
	 * @var int?
	 */
	private $_take = null;
	
	/**
	 * Skip n elements
	 *
	 * @var int?
	 */
	private $_skip = null;
	
	/**
	 * OrderBy expressions
	 *
	 * @var PHPLinq_Expression[]
	 */
	private $_orderBy = array();
	
	/**
	 * Create a new class instance
	 *
	 * @param string $name
	 * @return PHPLinq_ILinqProvider
	 */
	public function __construct($name) {
		$this->_from = $name;
		return $this;
	}
	
	/**
	 * Set source of data
	 *
	 * @param mixed $source
	 * @return PHPLinq_ILinqProvider
	 */
	public function in($source) {
		$this->_data = $source;
		return $this;
	}
	
	/**
	 * Select
	 *
	 * @param  string	$expression	Expression which creates a resulting element
	 * @return mixed
	 */
	public function select($expression){
		// Returnvalue
		$returnValue = array();
			
		// OrderBy set?
		if (is_array($this->_orderBy) && count($this->_orderBy) > 0) {
			// Create sorter
			$sorter = '';
			
			// Is there only one OrderBy expression?
			if (count($this->_orderBy) == 1) {
				// First sorter
				$sorter = $this->_orderBy[0]->getFunctionReference();
			} else {
				// Create OrderBy expression
				$compareCode = '';
				
				// Compile comparer function
				$compareCode = "
					\$result = null;
				";
				for ($i = 0; $i < count($this->_orderBy); $i++) {
					
					$f = substr($this->_orderBy[$i]->getFunctionReference(), 1); 
					$compareCode .= "
					\$result = call_user_func_array(chr(0).'$f', array({$this->_from}A, {$this->_from}B));
					if (\$result != 0) {
						return \$result;
					}
					";
					
				}
				$compareCode .= "return \$result;";
				$sorter = create_function($this->_from . 'A, ' . $this->_from . 'B', $compareCode);
			}
			
			// Sort!
			usort($this->_data, $sorter);
		}
		
		// Create selector expression
		$selector = new PHPLinq_Expression($expression, $this->_from);
		
		// Count elements
		$elementCount = 0;
		
		// Loop trough data source
		foreach ($this->_data as $value) {
			// Is it a valid element?
			$isValid = true;
			
			// Where expresion set? Evaluate it!
			if (!is_null($this->_where)) {
				$isValid = $this->_where->execute($value);
			}
			
			// The element is valid, check if it is our selection range
			if ($isValid) {
					
				// Skip element?
				if (!is_null($this->_skip) && $elementCount < $this->_skip) {
					$isValid = false;
				}
				
				// Take element?
				if (!is_null($this->_take) && count($returnValue) >= $this->_take) {
					$isValid = false;
					break;
				}

				// Next element
				$elementCount++;
				
				// Add the element to the return value if it is a valid element
				if ($isValid) {
					$returnValue[] = $selector->execute($value);
				}
			}
		}
		
		// Return value
		return $returnValue;
	}
	
	/**
	 * Where
	 *
	 * @param  string	$expression	Expression checking if an element should be contained
	 * @return PHPLinq_ILinqProvider
	 */
	public function where($expression) {
		$this->_where = new PHPLinq_Expression($expression, $this->_from);
		return $this;
	}
	
	/**
	 * Take $n elements
	 *
	 * @param int $n
	 */
	public function take($n) {
		$this->_take = $n;
		return $this;
	}
	
	/**
	 * Skip $n elements
	 *
	 * @param int $n
	 */
	public function skip($n) {
		$this->_skip = $n;
		return $this;
	}
	
	/**
	 * OrderBy
	 *
	 * @param  string	$expression	Expression to order elements by
	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
	 * @return PHPLinq_ILinqProvider
	 */
	public function orderBy($expression, $comparer = null) {
		$this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
		return $this;
	}
	
	/**
	 * OrderByDescending
	 *
	 * @param  string	$expression	Expression to order elements by
	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
	 * @return PHPLinq_ILinqProvider
	 */
	public function orderByDescending($expression, $comparer = null) {
		$this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
		return $this;
	}
	
	/**
	 * ThenBy
	 *
	 * @param  string	$expression	Expression to order elements by
	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
	 * @return PHPLinq_ILinqProvider
	 */
	public function thenBy($expression, $comparer = null) {
		$this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
		return $this;
	}
	
	/**
	 * ThenByDescending
	 *
	 * @param  string	$expression	Expression to order elements by
	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
	 * @return PHPLinq_ILinqProvider
	 */
	public function thenByDescending($expression, $comparer = null) {
		$this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
		return $this;
	}
}
